﻿using System;
using System.Collections.ObjectModel;
using System.Collections;
using System.ComponentModel;
using Silverlight.SqlStatementEvaluator;

namespace System.Data
{
    /// <summary>
    /// Represents a databindable, customized view of a DataTable for sorting, filtering, searching, editing, and navigation
    /// </summary>
    public class DataView : IDataSource, INotifyPropertyChanged
    {
        #region Data Fields
        /// <summary>
        /// Holds connector instance
        /// </summary>
        private Connector _connector;

        /// <summary>
        /// Holds collection of bindable objects
        /// </summary>
        private BindableObjectCollection _bindableObjectCollection;

        /// <summary>
        /// "HasErrors" property data field
        /// </summary>
        private bool _hasErrors;

        /// <summary>
        /// Holds ORDER BY closure
        /// </summary>
        private string _orderByClosure;

        /// <summary>
        /// Holds WHERE parameters
        /// </summary>
        private object[] _parameters;

        /// <summary>
        /// Holds DataRelations
        /// </summary>
        private DataRelationCollection _relations;

        /// <summary>
        /// "Rows" property data field
        /// </summary>
        private DataRowCollection _rows;

        /// <summary>
        /// "Table" property data field
        /// </summary>
        private DataTable _table;

        /// <summary>
        /// "ViewName" property data field
        /// </summary>
        private string _viewName;

        /// <summary>
        /// Holds WHERE closure
        /// </summary>
        private string _whereClosure;
        #endregion

        #region Events
        /// <summary>
        /// Requests Master DataRow for specified DataRelation
        /// </summary>
        internal event EventHandler<DataSetEventArgs> MasterDataRowCollectionRequest;

        /// <summary>
        /// INotifyPropertyChanged interface implementation
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        internal DataView()
        {
            this._rows = new DataRowCollection(false);
            this._bindableObjectCollection = new BindableObjectCollection();
            this._bindableObjectCollection.AddRecord += new EventHandler<EventArgs>(BindableObjectCollection_AddRecord);
        }

        /// <summary>
        /// Initializes a new instance of the DataView class with the specified DataTable
        /// </summary>
        /// <param name="table">DataTable on which DataView is based</param>
        /// <param name="relations">Collection of DataRelations</param>
        internal DataView(DataTable table, DataRelationCollection relations)
            : this()
        {
            // Validate arguments
            if (table == null)
            {
                throw new ArgumentNullException("table");
            }

            if (relations == null)
            {
                throw new ArgumentNullException("relations");
            }

            this._table = table;
            this._relations = relations;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets "HasErrors" flag value
        /// </summary>
        public bool HasErrors
        {
            get
            {
                return this._hasErrors;
            }
        }

        /// <summary>
        /// Gets "HasNoErrors" flag value
        /// </summary>
        public bool HasNoErrors
        {
            get
            {
                return !this._hasErrors;
            }
        }

        /// <summary>
        /// Data source name
        /// </summary>
        public string Name
        {
            get
            {
                return this._viewName;
            }
        }

        /// <summary>
        /// Gets rows
        /// </summary>
        public DataRowCollection Rows
        {
            get
            {
                return this._rows;
            }
        }

        /// <summary>
        /// Gets or sets the source DataTable
        /// </summary>
        public DataTable Table
        {
            get
            {
                return this._table;
            }
            set
            {
                // Validate argument
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }

                this._table = value;
            }
        }

        /// <summary>
        /// Gets or sets DataView name
        /// </summary>
        public string ViewName
        {
            get
            {
                return this._viewName;
            }
            set
            {
                // Validate argument
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentNullException("value");
                }

                this._viewName = value;
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Copies DataView structure
        /// </summary>
        /// <param name="addPrefix">Add "CopyOf" prefix to view name</param>
        /// <returns>Empty DataView</returns>
        public DataView Clone(bool addPrefix)
        {
            DataView dataView = new DataView();
            dataView._hasErrors = this._hasErrors;
            if (addPrefix)
            {
                dataView.ViewName = "CopyOf" + this.ViewName;
            }
            else
            {
                dataView.ViewName = this.ViewName;
            }

            return dataView;
        }

        /// <summary>
        /// Copies DataView structure and data
        /// </summary>
        /// <param name="addPrefix">Add "CopyOf" prefix to table name</param>
        /// <returns>DataView copy</returns>
        public IDataSource Copy(bool addPrefix)
        {
            DataView dataView = this.Clone(addPrefix);
            dataView._table = (DataTable)this._table.Copy(addPrefix);

            // Compare every DataRow with DataRow from underlined DataTable
            for (int i = 0; i < this._rows.Count; i++)
            {
                for (int j = 0; j < this._table.Rows.Count; j++)
                {
                    if (this._rows[i] == this._table.Rows[j])
                    {
                        // If they are the same then add DataRow with the same index to copied DataView
                        dataView._rows.Add(dataView._table.Rows[j]);
                        break;
                    }
                }
            }

            return dataView;
        }

        /// <summary>
        /// Selects DataRows according to Master DataRow
        /// </summary>
        internal void From()
        {
            // Remove existing rows
            this._rows.Clear();

            if (this._relations.Count == 0)
            {
                // Set IsChildRow to all DataRows
                this._table.SetChildRows(true);
            }
            else
            {
                // Reset IsChildRow to all DataRows
                this._table.SetChildRows(false);

                // Set IsChild according to all DataRelations
                foreach (DataRelation dataRelation in this._relations)
                {
                    if (MasterDataRowCollectionRequest != null)
                    {
                        DataSetEventArgs dataSetEventArgs = new DataSetEventArgs();
                        dataSetEventArgs.Relation = dataRelation;

                        // Request Master DataRowCollection for selected DataRelation
                        MasterDataRowCollectionRequest(this, dataSetEventArgs);

                        // Find detail rows for master row
                        this._table.ApplyDatarelationToDataTable(dataSetEventArgs.DataRowCollection, dataRelation);
                    }
                }
            }
        }

        /// <summary>
        /// Provides collection of bindable objects for binding
        /// </summary>
        /// <param name="connector">Connector instance</param>
        /// <returns>Collection of generated bindable objects</returns>
        public IList GetBindableData(Connector connector)
        {
            // Validate argument
            if (connector == null)
            {
                throw new ArgumentNullException("connector");
            }

            this._connector = connector;
            this.GetBindableData();
            return this._bindableObjectCollection;
        }

        /// <summary>
        /// Generates bindable objects
        /// </summary>
        internal void GetBindableData()
        {
            this._bindableObjectCollection.Clear();
            for (int i = 0; i < this._rows.Count; i++)
            {
                this._bindableObjectCollection.Add(this._connector.GetBindableData(this._rows[i], false));
            }
        }

        /// <summary>
        /// Raises PropertChanged event
        /// </summary>
        /// <param name="propertyName">Property name</param>
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Orders DataRows according ORDER BY closure
        /// </summary>
        public void OrderBy()
        {
            // Do nothing if order wasn't specified
            if (string.IsNullOrEmpty(this._orderByClosure))
            {
                return;
            }

            //Sorter.OrderBy(this._rows, this._orderByClosure);
        }

        /// <summary>
        /// Orders DataRows according ORDER BY closure
        /// </summary>
        /// <param name="orderByClosure">ORDER BY closure</param>
        public void OrderBy(string orderByClosure)
        {
            this._orderByClosure = orderByClosure;
            this.OrderBy();
        }

        /// <summary>
        /// Recalculates table errors
        /// </summary>
        private void RecalculateErrors()
        {
            // Prepare result variable
            bool hasErrors = false;

            // Check all rows
            for (int j = 0; j < this._rows.Count; j++)
            {
                if (this._rows[j].HasErrors)
                {
                    hasErrors = true;
                    break;
                }
            }

            // Change "HasErrors" property if needed
            if (this._hasErrors != hasErrors)
            {
                this._hasErrors = hasErrors;
                this.OnPropertyChanged("HasErrors");
                this.OnPropertyChanged("HasNoErrors");
            }
        }

        /// <summary>
        /// Filters DataRows according to WHERE closure
        /// </summary>
        internal void Where()
        {
            // Do nothing if WHERE closure wasn't specified
            if (string.IsNullOrEmpty(this._whereClosure))
            {
                // Add child rows to DataView
                for (int j = 0; j < this._table.Rows.Count; j++)
                {
                    if (this._table.Rows[j].IsChildRow)
                    {
                        this._rows.Add(this._table.Rows[j]);
                    }
                }
            }
            else
            {
               // Evaluator.Where(this, this._whereClosure, this._parameters);
            }
        }

        /// <summary>
        /// Filters DataRows according to WHERE closure
        /// </summary>
        /// <param name="whereClosure">WHERE closure</param>
        /// <param name="parameters">Array of parameters</param>
        internal void Where(string whereClosure, params object[] parameters)
        {
            this._whereClosure = whereClosure;
            this._parameters = parameters;
            this.Where();
        }
        #endregion

        #region Event Handlers
        /// <summary>
        /// Handles BindableObjectCollection.AddRecord event
        /// </summary>
        /// <param name="sender">Event source</param>
        /// <param name="e">Event arguments</param>
        void BindableObjectCollection_AddRecord(object sender, EventArgs e)
        {
            // Add new DataRow to DataTable
            DataRow dataRow = this._table.NewRow();
            this._table.Rows.Add(dataRow);

            // Add new BindableObject to BindableObjectCollection
            BindableObject bindableObject = (BindableObject)dataRow.GetBindableData(this._connector);
            this._bindableObjectCollection.Add(bindableObject);
        }
        #endregion
    }
}
